package com.daffodil.storage.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;

import com.daffodil.storage.exception.BaseException;
import com.daffodil.storage.model.MediaMimetype;
import com.daffodil.storage.model.Page;
import com.daffodil.storage.model.StorageResult;
import com.daffodil.storage.properties.MinioProperties;
import com.daffodil.storage.properties.StorageProperties;
import com.daffodil.storage.service.AStorageService;
import com.daffodil.storage.util.StorageUtils;

import io.minio.DownloadObjectArgs;
import io.minio.ListObjectsArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.Result;
import io.minio.messages.Item;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * Minio对象存储
 * @author yweijian
 * @date 2022年12月8日
 * @version 2.0.0
 * @description
 */
@Slf4j
@Service
@Configuration
@ConditionalOnProperty(name = StorageProperties.NAME, havingValue = StorageProperties.MINIO)
public class StorageMinioServiceImpl extends AStorageService {

    @Autowired
    private StorageProperties storageProperties;

    @Autowired
    private MinioClient minioClient;

    /** 限定列举文件的最大个数 */
    private static final Integer MAXKEYS = 1000;

    @Override
    public StorageResult upolad(StorageResult result, InputStream inputStream) {
        log.debug("Minio对象存储 > 上传对象 > fileName={}", result.getOrigin());

        MinioProperties properties = storageProperties.getMinio();
        result.setUrl(properties.getDomain() + StrUtil.SLASH + result.getKey());

        try {
            PutObjectArgs putObjectOptions = PutObjectArgs.builder().bucket(properties.getBucketName()).object(result.getKey()).stream(inputStream, result.getSize(), -1).build();
            ObjectWriteResponse response = minioClient.putObject(putObjectOptions);
            if(ObjectUtil.isNotNull(response)) {
                result.setETag(response.etag());
                log.debug("MinIO对象存储 > 上传对象 > eTag={}", response.etag());
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        }

        log.debug("MinIo对象存储 > 上传对象 > result={}", result.toString());
        return result;
    }

    @Override
    public List<StorageResult> list(String folder, String name, Page page) {
        MinioProperties properties = storageProperties.getMinio();
        String prefix = StorageUtils.getPrefix(folder, name);
        Integer maxKeys = Math.min(page.getToIndex(), MAXKEYS);
        ListObjectsArgs listObjectsArgs = ListObjectsArgs.builder()
                .bucket(properties.getBucketName())
                .prefix(prefix.toString())
                .maxKeys(maxKeys)
                .recursive(false)
                .delimiter(StrUtil.SLASH)
                .build();

        List<StorageResult> results = new ArrayList<StorageResult>();
        Iterable<Result<Item>> objectResults = minioClient.listObjects(listObjectsArgs);
        //对象存储数据
        List<Result<Item>> resultItems = new ArrayList<Result<Item>>();
        objectResults.forEach(item -> {
            try {
                String key = item.get().objectName();
                if(!key.endsWith(StrUtil.SLASH)) {
                    resultItems.add(item);
                }else {
                    //目录数据
                    StorageResult result = StorageResult.builder().name(FileNameUtil.getName(key)).origin(FileNameUtil.getName(key))
                            .prefix(FileNameUtil.getPrefix(key)).suffix(FileNameUtil.getSuffix(key)).size(0L).mime(StrUtil.EMPTY).key(key)
                            .url(properties.getDomain() + StrUtil.SLASH + key).metadata(MapUtil.empty()).build();
                    results.add(result);
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new BaseException(e.getMessage());
            }
        });

        page.setTotalRow(resultItems.size());
        int fromIndex = page.getFromIndex();
        int toIndex = Math.min(page.getToIndex(), resultItems.size());
        if(fromIndex > toIndex) {
            return results;
        }

        for(int i = fromIndex; i < toIndex; i++) {
            try {
                Result<Item> item = resultItems.get(i);
                String key = item.get().objectName();
                StorageResult result = StorageResult.builder().name(FileNameUtil.getName(key)).origin(FileNameUtil.getName(key))
                        .prefix(FileNameUtil.getPrefix(key)).suffix(FileNameUtil.getSuffix(key)).size(item.get().size())
                        .mime(MediaMimetype.getInstance().getMimetype(FileNameUtil.getSuffix(key))).key(key)
                        .url(properties.getDomain() + StrUtil.SLASH + key).metadata(MapUtil.empty()).build();
                results.add(result);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                throw new BaseException(e.getMessage());
            }
        }

        return results;
    }

    @Override
    public void download(String key, HttpServletResponse response) {
        MinioProperties properties = storageProperties.getMinio();
        String fileName = FileNameUtil.getName(key);
        if(ObjectUtil.isEmpty(fileName)) {
            return;
        }
        String filePath = System.getProperty("java.io.tmpdir") + fileName;
        File file = new File(filePath);
        DownloadObjectArgs downloadObjectArgs = DownloadObjectArgs.builder().
                bucket(properties.getBucketName()).
                object(key).
                filename(filePath).
                build();

        try {
            minioClient.downloadObject(downloadObjectArgs);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        }

        try {
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE);
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(fileName, StandardCharsets.UTF_8).build().toString());
            FileUtil.writeToStream(filePath, response.getOutputStream());
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        } finally {
            if(file != null && file.exists() && file.canExecute()) {
                file.delete();
            }
        }
    }

    @Override
    public void remove(String key) {
        MinioProperties properties = storageProperties.getMinio();
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(properties.getBucketName()).object(key).build();
        try {
            minioClient.removeObject(removeObjectArgs);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        }
    }

}
